// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fbl/auto_call.h>
#include <fuchsia/hardware/light/c/fidl.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>

static int name_command(zx_handle_t svc, int argc, const char* argv[]) {
  if (argc != 1) {
    fprintf(stderr, "expected one argument\n");
    return -1;
  }
  uint32_t index;
  if (sscanf(argv[0], "%u", &index) != 1) {
    fprintf(stderr, "could not parse index %s\n", argv[0]);
    return -1;
  }

  char buffer[fuchsia_hardware_light_LIGHT_NAME_LEN];
  size_t actual;
  zx_status_t status2;
  auto status =
      fuchsia_hardware_light_LightGetName(svc, index, &status2, buffer, sizeof(buffer), &actual);
  if (status == ZX_OK) {
    status = status2;
  }
  if (status == ZX_OK) {
    printf("%s\n", buffer);
    return 0;
  } else {
    fprintf(stderr, "fuchsia_hardware_light_DeviceGetName failed: %s\n",
            zx_status_get_string(status));
    return -1;
  }
}

static int count_command(zx_handle_t svc, int argc, const char* argv[]) {
  uint32_t count;
  auto status = fuchsia_hardware_light_LightGetCount(svc, &count);
  if (status == ZX_OK) {
    printf("%u\n", count);
    return 0;
  } else {
    fprintf(stderr, "fuchsia_hardware_light_DeviceGetCount failed: %s\n",
            zx_status_get_string(status));
    return -1;
  }
}

static int has_capability_command(zx_handle_t svc, int argc, const char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "expected two arguments\n");
    return -1;
  }
  uint32_t index;
  if (sscanf(argv[0], "%u", &index) != 1) {
    fprintf(stderr, "could not parse index %s\n", argv[0]);
    return -1;
  }
  fuchsia_hardware_light_Capability capability;
  if (!strcmp(argv[1], "brightness")) {
    capability = fuchsia_hardware_light_Capability_BRIGHTNESS;
  } else if (!strcmp(argv[1], "rgb")) {
    capability = fuchsia_hardware_light_Capability_RGB;
  } else {
    fprintf(stderr, "unknown capability \"%s\"\n", argv[1]);
    return -1;
  }

  zx_status_t status2;
  bool has;
  auto status = fuchsia_hardware_light_LightHasCapability(svc, index, capability, &status2, &has);
  if (status == ZX_OK) {
    status = status2;
  }
  if (status == ZX_OK) {
    printf("%s\n", (has ? "true" : "false"));
    return 0;
  } else {
    fprintf(stderr, "fuchsia_hardware_light_LightHasCapability failed: %s\n",
            zx_status_get_string(status));
    return -1;
  }
}

static int get_value_command(zx_handle_t svc, int argc, const char* argv[]) {
  if (argc != 1) {
    fprintf(stderr, "expected one argument\n");
    return -1;
  }
  uint32_t index;
  if (sscanf(argv[0], "%u", &index) != 1) {
    fprintf(stderr, "could not parse index %s\n", argv[0]);
    return -1;
  }

  zx_status_t status2;
  uint8_t value;
  auto status = fuchsia_hardware_light_LightGetSimpleValue(svc, index, &status2, &value);
  if (status == ZX_OK) {
    status = status2;
  }
  if (status == ZX_OK) {
    printf("%u\n", value);
    return 0;
  } else {
    fprintf(stderr, "fuchsia_hardware_light_LightGetSimpleValue failed: %s\n",
            zx_status_get_string(status));
    return -1;
  }
}

static int set_value_command(zx_handle_t svc, int argc, const char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "expected two arguments\n");
    return -1;
  }
  uint32_t index;
  if (sscanf(argv[0], "%u", &index) != 1) {
    fprintf(stderr, "could not parse index %s\n", argv[0]);
    return -1;
  }
  uint32_t value;
  if (sscanf(argv[1], "%u", &value) != 1) {
    fprintf(stderr, "could not parse value %s\n", argv[1]);
    return -1;
  }
  if (value >= UINT8_MAX) {
    fprintf(stderr, "value %u out of range\n", value);
    return -1;
  }

  zx_status_t status2;
  auto status =
      fuchsia_hardware_light_LightSetSimpleValue(svc, index, static_cast<uint8_t>(value), &status2);
  if (status == ZX_OK) {
    status = status2;
  }
  if (status == ZX_OK) {
    return 0;
  } else {
    fprintf(stderr, "fuchsia_hardware_light_LightSetSimpleValue failed: %s\n",
            zx_status_get_string(status));
    return -1;
  }
}

struct Command {
  const char* name;
  int (*command)(zx_handle_t svc, int argc, const char* argv[]);
  const char* description;
};

static Command commands[] = {
    {"name", name_command, "name <index> - returns the name of the light"},
    {"count", count_command, "count - returns the number of physical lights"},
    {"capability", has_capability_command,
     "capability <index> [brightness|rgb] - returns true if the light has the capability"},
    {"get-value", get_value_command, "get-value <index> - returns the current value of the light"},
    {"set-value", set_value_command,
     "set-value <index> <value> - sets the current value of the light"},
    {},
};

static void usage(void) {
  fprintf(stderr, "usage: \"light [-d <dev-file>] <command>\", where command is one of:\n");

  Command* command = commands;
  while (command->name) {
    fprintf(stderr, "    %s\n", command->description);
    command++;
  }
}

int main(int argc, const char** argv) {
  if (argc < 2) {
    usage();
    return -1;
  }
  argv++;
  argc--;

  const char* dev_file_name = "000";
  if (!strcmp(argv[0], "-d")) {
    if (argc < 3) {
      usage();
      return -1;
    }

    dev_file_name = argv[1];
    if (strlen(dev_file_name) != 3) {
      usage();
      return -1;
    }

    argv += 2;
    argc -= 2;
  }

  char path[PATH_MAX];
  snprintf(path, sizeof(path), "/dev/class/light/%s", dev_file_name);
  int fd = open(path, O_RDWR);
  if (fd < 0) {
    printf("Error opening %s\n", path);
    return -1;
  }

  zx_handle_t svc;
  zx_status_t status = fdio_get_service_handle(fd, &svc);
  if (status != ZX_OK) {
    close(fd);
    printf("Error opening FIDL connection for %s\n", path);
    return -1;
  }
  auto cleanup = fbl::MakeAutoCall([fd, svc]() {
    zx_handle_close(svc);
    close(fd);
  });

  const char* command_name = argv[0];
  argv++;
  argc--;
  Command* command = commands;
  while (command->name) {
    if (!strcmp(command_name, command->name)) {
      return command->command(svc, argc, argv);
    }
    command++;
  }
  // if we fall through, print usage
  usage();
  return -1;
}
